qtre.camera = function(eye, target, up, opt_color, opt_width, opt_height){
	if(qtre.core.isset(eye)){
		/**
		 *@var String
		 */
		this.name = "";
		//is this right ?
		this.width = opt_width || qtre.core.getWidth();

		this.height = opt_height || qtre.core.getHeight();
		/**
		 *@var Array(4)
		 */
		this.eye = eye;
		/**
		 *@var Array(4)
		 */
		this.target = target;
		/**
		 *@var Array(4)
		 */
		this.up = up;
		/**
		 *@var Float32Array(4)
		 */
		this.color = opt_color || new Float32Array([0, 0, 0.7, 1]);
		/**
		 *@var qtre.sceneNode
		 */
		this.owner = null;
		/**
		 *@var Float32Array(16)
		 */
		this.viewProjectionMatrix = new Float32Array(16);
		/**
		 *@var Float32Array(16)
		 */
		this._viewMatrix = new Float32Array(16);

		this._viewMatrixTransformed = null;
		/**
		 *@var Float32Array(16)
		 */
		this.projectionMatrix = new Float32Array(16);
		/**
		 *@var qtre.renderable
		 */
		this.skyBox = null;
		/**
		 * renderQueue stores all the material. Each frame the scene manager will view
		 * the renderQueue and render each renderable object binded to the material.
		 */
		this.renderQueue = [];

		//initializing
		this.updateProjection();
		
		this.updateView();
		
		this._onRenderBegins = [];

		this._onRenderEnds = [];
	}
}

qtre.camera.prototype = {
	addRenderBeginCallback : function(callback){
		this._onRenderBegins.push(callback);
	},

	removeRenderBeginCallback : function(callback){
		for(var i = 0;  i < this._onRenderBegins.length; i++){
			if(this._onRenderBegins[i] == callback){
				delete this._onRenderBegins[i];
			}
		}
	},

	addRenderEndCallback : function(callback){
		this._onRenderEnds.push(callback);
	},

	removeRenderEndCallback : function(callback){
		for(var i = 0;  i < this._onRenderEnds.length; i++){
			if(this._onRenderEnds[i] == callback){
				delete this._onRenderEnds[i];
			}
		}
	},

	onRenderBegin : function(){
		for(var i = 0;  i < this._onRenderBegins.length; i++){
			this._onRenderBegins[i].call(this);
		}
	},

	onRenderEnd : function(){
		for(var i = 0;  i < this._onRenderEnds.length; i++){
			this._onRenderEnds[i].call(this);
		}
	},
	/**
	 * must be overridden
	 */
	updateProjection : function(){},

	updateView : function(){
		tdl.fast.matrix4.lookAt(this._viewMatrix,
								this.eye,
								this.target,
								this.up);
	},

	setSkyBox : function(skyBoxUrls){
		if(qtre.core.isset(this.far)){
			radius = this.far - 100;
		}
		var skyBoxIndices = tdl.primitives.createSphere(radius, 20, 20);
		delete skyBoxIndices.texCoord;
		
		var skyBoxTextures = tdl.textures.loadTexture(skyBoxUrls);
		var program = tdl.programs.loadProgram("\
											attribute vec4 position;\n\
											attribute vec3 normal;\n\
											uniform mat4 worldViewProjection;\n\
											uniform mat4 world;\n\
											uniform mat4 worldInverseTranspose;\n\
											varying vec4 v_position;\n\
											varying vec3 v_normal;\n\
											void main() {\n\
												v_position = world * position;\n\
												v_normal = (worldInverseTranspose * vec4(normal, 0.0)).xyz;\n\
												gl_Position = worldViewProjection * position;\n\
											}"
											,
											"#ifdef GL_ES\n\
											precision highp float;\n\
											#endif\n\
											uniform samplerCube skybox;\n\
											uniform mat4 viewInverse;\n\
											varying vec4 v_position;\n\
											varying vec3 v_normal;\n\
											void main() {\n\
												vec3 surfaceToView = normalize(viewInverse[3].xyz - v_position.xyz);\n\
												vec3 reflect = reflect(surfaceToView, v_normal);\n\
												gl_FragColor = textureCube(skybox, reflect);\n\
											}");
		var skyBoxMaterial = new qtre.material(this.name + "_skybox", program);
		var skyBox = new qtre.renderable(this.name + "_skybox", skyBoxIndices);
		skyBox.bindMaterial(skyBoxMaterial);
		this.skyBox = skyBox;
		var sceneNode = new qtre.sceneNode(this.name + "_skybox");
		sceneNode.attachObject(skyBox);

		var camera = this;
		skyBox.addRenderBeginCallback(function(material){
			var worldViewProjection = new Float32Array(16);
			tdl.fast.matrix4.mul(worldViewProjection, this.worldMatrix, camera.getViewProjection());
			material.updateUniforms({
				worldViewProjection : worldViewProjection,
				world : this.worldMatrix,
				worldInverseTranspose : this.getWorldInverseTranspose(),
				viewInverse : camera.getViewInverse(),
				skybox : skyBoxTextures
			})
		});
	},

	transformView : function(){
		var sceneNode = this.owner;
		var worldMatrix = new Float32Array(16);
		tdl.fast.matrix4.copy(worldMatrix, sceneNode.worldMatrix);
		while(sceneNode.parent){
			tdl.fast.matrix4.mul(worldMatrix, worldMatrix, sceneNode.parent.worldMatrix);
			sceneNode = sceneNode.parent;
		}
		var worldInverseMatrix = new Float32Array(16);
		tdl.fast.matrix4.inverse(worldInverseMatrix, worldMatrix);
		var viewMatrix = new Float32Array(16);
		tdl.fast.matrix4.copy(viewMatrix, this._viewMatrix);
		tdl.fast.matrix4.mul(viewMatrix, worldInverseMatrix, viewMatrix);
		this._viewMatrixTransformed = viewMatrix;
	},

	getView : function(){
		return this._viewMatrixTransformed;
	},

	/**
	 *@return Float32Array(16)
	 */
	getViewInverse : function(){
		var viewInverse = new Float32Array(16);
		tdl.fast.matrix4.inverse(viewInverse, this._viewMatrixTransformed);
		return viewInverse;
	},
	
	getViewProjection : function(){
		var viewProjection = new Float32Array(16);
		tdl.fast.matrix4.mul(viewProjection, this._viewMatrixTransformed, this.projectionMatrix);
		return viewProjection;
	},
	
	render : function(){
		//update the viewMatrix
		if(!this.owner){
			return ;
		}
		this.transformView();
		//clear the buffers
		qtre.core.gl.clearColor(this.color[0], this.color[1], this.color[2], this.color[3]);
		qtre.core.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
		gl.enable(gl.BLEND);
		gl.enable(gl.DEPTH_TEST);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
		
		//draw the skyBox
		if(this.skyBox){
			for(var materialName in this.skyBox.materials){
				this._renderMaterial(this.skyBox.materials[materialName]);
			}
		}
		
		//draw other renderable objects
		for(var materialName in this.renderQueue){
			this._renderMaterial(this.renderQueue[materialName]);
		}
	},
	
	_renderMaterial : function(material){
		material.onRenderBegin();
		material.bind();
		for(var renderableName in material._renderableQueue){
			var renderable = material._renderableQueue[renderableName];
			renderable.updateWorldMatrix();
			renderable.onRenderBegin(material);
			material.applyUniforms();
			renderable.render(material);
			renderable.onRenderEnd(material);
		}
		material.onRenderEnd();
	}
}

/***********************************************
 * perspective view camera
 ***********************************************/
qtre.cameraPerspective = function(eye, target, up, fov, near, far){
	this.fov = fov;
	this.near = near;
	this.far = far;
	//get the properties
	qtre.camera.call(this, eye, target, up);
}
//inherit from qtre.camera
qtre.cameraPerspective.prototype = new qtre.camera();

/**
 * @override
 * 
 */
qtre.cameraPerspective.prototype.updateProjection = function(){
	tdl.fast.matrix4.perspective(
				this.projectionMatrix,
				this.fov,
				this.width/this.height,
				this.near,
				this.far);
}

/***********************************************
 * ortho view camera
 ***********************************************/
qtre.cameraOrtho = function(eye, target, up, left, right, bottom, top, near, far){
	this.left = left;
	this.right = right;
	this.bottom = bottom;
	this.top = top;
	this.near = near;
	this.far = far;
	qtre.camera.call(this, eye, target, up);
}//inherit from qtre.camera
qtre.cameraOrtho.prototype = new qtre.camera();

/**
 * @override
 *
 */
qtre.cameraOrtho.prototype.updateProjection = function(){
	tdl.fast.matrix4.ortho(
				this.projectionMatrix,
				this.left,
				this.right,
				this.bottom,
				this.top,
				this.near,
				this.far);
}
